---
import PageIntro from "../../components/PageIntro.astro";
import Layout from "../../layouts/Layout.astro";
import Note from "../../components/Note.astro";
import Heading from "../../components/Heading.astro";
import { Code } from "astro:components";
---

<Layout
  meta={{
    title: "Frameworks guide",
    description:
      "Find out how to integrate Cally's components with frontend frameworks like React and Vue, and how to improve TypeScript support.",
  }}
>
  <h1 slot="intro">Integrating with frameworks</h1>
  <PageIntro slot="intro">How to use with React/Vue/Svelte</PageIntro>

  <p>
    It is not necessary to use a framework with Cally. However, by virtue of
    being written as web components,
    <strong>Cally is framework-agnostic and can be used anywhere</strong>.
  </p>

  <p>
    Most frameworks support web components out of the box, requiring
    little-to-no setup. Here we will walk through how to use Cally in React,
    Vue, and Svelte. The process should be similar for other frameworks.
  </p>

  <Heading level={2}>React</Heading>

  <p>
    As of version 19, React has <a
      href="https://custom-elements-everywhere.com/#react"
      >excellent support for web components</a
    >. They can now be used directly, with no wrappers or setup necessary.
  </p>

  <Code
    lang="jsx"
    code={`
import { useState } from "react";
import { createRoot } from 'react-dom/client';
import "cally";

function App() {
  const [value, setValue] = useState("");

  return (
    <>
      <p>Value is: {value}</p>
      <calendar-range
        value={value}
        onchange={(event) => setValue(event.target.value)}
      >
        <calendar-month />
        <calendar-month offset={1} />
      </calendar-range>
    </>
  );
}

const root = createRoot(document.getElementById("root"));
root.render(<App />)
`.trim()}
  />

  <Note>
    <strong>Note:</strong> React requires that event listeners for web components
    use the event name <strong>verbatim</strong>. For instance if you have an
    event named <code>fooBar</code> in your web component, you must listen for the
    <code>onfooBar</code> event. This is unusual for React, which typically expects
    events to be like <code>onFooBar</code>.
  </Note>

  <Heading id="react-typescript" level={3}>TypeScript</Heading>

  <p>
    If you are using TypeScript, you can augment React's types to add
    type-checking and improve your editor experience. Cally exports types for
    each component's props making this a simple, one-time procedure.
  </p>

  <p>
    First you should create a <code>d.ts</code> file in your React project, with
    any name you like. For this example let's call it <code>globals.d.ts</code>.
    In that file, paste the following code:
  </p>

  <Code
    lang="tsx"
    code={`
import type {
  CalendarRangeProps,
  CalendarMonthProps,
  CalendarDateProps,
  CalendarMultiProps,
} from "cally";

type MapEvents<T> = {
  [K in keyof T as K extends \`on\${infer E}\` ? \`on\${Lowercase<E>}\` : K]: T[K];
};

declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "calendar-month": MapEvents<CalendarMonthProps> &
        React.HTMLAttributes<HTMLElement>;
      "calendar-range": MapEvents<CalendarRangeProps> &
        React.HTMLAttributes<HTMLElement>;
      "calendar-date": MapEvents<CalendarDateProps> &
        React.HTMLAttributes<HTMLElement>;
      "calendar-multi": MapEvents<CalendarMultiProps> &
        React.HTMLAttributes<HTMLElement>;
    }
  }
}
`.trim()}
  />

  <Note>
    This uses TypeScript's declaration merging feature. You can read more about
    it in the <a
      href="https://www.typescriptlang.org/docs/handbook/declaration-merging.html"
      >TypeScript Handbook</a
    >.
  </Note>

  <p>
    Finally, you must add this to the <code>compilerOptions.types</code> field in
    your
    <code>tsconfig.json</code>:
  </p>

  <Code
    lang="json"
    code={`
{
  // ...
  "compilerOptions": {
    // ...
    "types": ["./globals.d.ts"]
  }
}
`.trim()}
  />

  <Heading level={2}>Vue</Heading>

  <p>
    Vue has <a href="https://custom-elements-everywhere.com/#vue"
      >excellent support for web components</a
    >. If you haven't already, you need to <a
      href="https://vuejs.org/guide/extras/web-components#skipping-component-resolution"
      >configure vue</a
    > to understand web components. After that, they can be used directly.
  </p>

  <Code
    lang="vue"
    code={`
<script setup>
import 'cally';
</script>
<template>
  <calendar-range :months="2">
    <calendar-month />
    <calendar-month :offset="1" />
  </calendar-range>
</template>
`.trim()}
  />

  <Heading id="vue-usage" level={3}>Usage with <code>v-model</code></Heading>

  <p>
    The <code>{`<calendar-date>`}</code> and <code>{`<calendar-range>`}</code> components
    emit <code>change</code> events when their value changes. You can use the
    <code>v-model</code> directive to bind <code>ref</code>s to these events.
  </p>

  <p>
    As noted in the Vue docs, <code>v-model</code> listens for
    <code>input</code> events by default. But by using the
    <a href="https://vuejs.org/guide/essentials/forms#lazy"
      ><code>.lazy</code> modifier</a
    >, it will listen for <code>change</code> events.
  </p>

  <Code
    lang="vue"
    code={`
<script setup>
import 'cally';
const selected = ref("")
</script>
<template>
  <p>Selected range: {{ selected }}</p>

  <calendar-range :months="2" v-model.lazy="selected">
    <calendar-month />
    <calendar-month :offset="1" />
  </calendar-range>
</template>
`.trim()}
  />

  <p>
    You are not required to use <code>v-model</code>. You can listen for events
    yourself if you prefer:
  </p>

  <Code
    lang="vue"
    code={`
<script setup>
import 'cally';

const selected = ref("")
function onChange(event) {
  selected.value = event.target.value
}
</script>
<template>
  <p>Selected range: {{ selected }}</p>

  <calendar-range :months="2" :value="selected" @change="onChange">
    <calendar-month />
    <calendar-month :offset="1" />
  </calendar-range>
</template>
`.trim()}
  />

  <Heading id="vue-typescript" level={3}>TypeScript</Heading>

  <p>
    If you are using TypeScript, you can augment Vue's types to add
    type-checking and improve your editor experience. Cally exports types for
    each component's props making this a simple, one-time procedure.
  </p>

  <p>
    First you should create a <code>d.ts</code> file in your Vue project, with any
    name you like. For this example let's call it <code>globals.d.ts</code>. In
    that file, paste the following code:
  </p>

  <Code
    lang="typescript"
    code={`
import type { DefineComponent } from "vue";
import type {
  CalendarRangeProps,
  CalendarMonthProps,
  CalendarDateProps,
} from "cally";

interface CallyComponents {
  "calendar-range": DefineComponent<CalendarRangeProps>;
  "calendar-date": DefineComponent<CalendarDateProps>;
  "calendar-month": DefineComponent<CalendarMonthProps>;
}

declare module "vue" {
  interface GlobalComponents extends CallyComponents {}
}

declare global {
  namespace JSX {
    interface IntrinsicElements extends CallyComponents {}
  }
}
`.trim()}
  />

  <Note>
    This uses TypeScript's declaration merging feature. You can read more about
    it in the <a
      href="https://www.typescriptlang.org/docs/handbook/declaration-merging.html"
      >TypeScript Handbook</a
    >.
  </Note>

  <p>
    Finally, you must add this to the <code>compilerOptions.types</code> field in
    your
    <code>tsconfig.json</code>:
  </p>

  <Code
    lang="json"
    code={`
{
  // ...
  "compilerOptions": {
    // ...
    "types": ["./globals.d.ts"]
  }
}
`.trim()}
  />

  <p>
    Now you will get type-checking for both props and events, along with
    auto-complete in your editor.
  </p>

  <Heading level={2}>Svelte</Heading>

  <p>
    Svelte has <a href="https://custom-elements-everywhere.com/#svelte"
      >excellent support for web components</a
    >. There is no setup required to start using Web Components.
  </p>

  <Code
    lang="svelte"
    code={`
<script lang="ts">
  import "cally";
</script>

<calendar-range months={2}>
  <calendar-month></calendar-month>
  <calendar-month offset={1}></calendar-month>
</calendar-range>
`.trim()}
  />

  <Heading id="svelte-typescript" level={3}>TypeScript</Heading>

  <p>
    If you are using TypeScript, you can augment Svelte's types to add
    type-checking and improve your editor experience. Cally exports types for
    each component's props making this a simple, one-time procedure.
  </p>

  <p>
    First you should create a <code>d.ts</code> file in your Svelte project, with
    any name you like. For this example let's call it <code>globals.d.ts</code>.
    In that file, paste the following code:
  </p>

  <Code
    lang="typescript"
    code={`
import type {
  CalendarRangeProps,
  CalendarMonthProps,
  CalendarDateProps,
} from "cally";

type MapEvents<T> = {
  [K in keyof T as K extends \`on\${infer E}\` ? \`on:\${Lowercase<E>}\` : K]: T[K];
};

declare module "svelte/elements" {
  interface SvelteHTMLElements {
    "calendar-range": MapEvents<CalendarRangeProps>;
    "calendar-month": MapEvents<CalendarMonthProps>;
    "calendar-date": MapEvents<CalendarDateProps>;
  }
}
`.trim()}
  />

  <Note>
    This uses TypeScript's declaration merging feature. You can read more about
    it in the <a
      href="https://www.typescriptlang.org/docs/handbook/declaration-merging.html"
      >TypeScript Handbook</a
    >.
  </Note>

  <p>
    Finally, you must add this to the <code>compilerOptions.types</code> field in
    your
    <code>tsconfig.json</code>:
  </p>

  <Code
    lang="json"
    code={`
{
  // ...
  "compilerOptions": {
    // ...
    "types": ["./globals.d.ts"]
  }
}
`.trim()}
  />

  <p>
    Now you will get type-checking for both props and events, along with
    auto-complete in your editor.
  </p>
</Layout>
